#include <iostream>

#include "eda.h"
#include "loNetList.hpp"
#include "loScriptMove.hpp"
#include "loScriptMoveNext.hpp"
#include "loScriptMoveRandom.hpp"
#include "sisInterface.h"

#define N 10

class loHC: public edaHC {
public:
  loHC(): edaHC() {
    sisInit();
  }
  loHC( edaMove *move, edaMoveGen *next, edaMoveSelect *select )
    : edaHC( move, next, select ) {}
  loHC( const loHC &hc)
    : edaHC( hc ) {}
  ~loHC() {
    sisEnd();
  }

  setClassID( _USERCLASSID_ + _CLSID_LO_HC_ );
};

class loSA: public edaSA {
public:
  loSA(): edaSA() {
    sisInit();
  }
  loSA( edaMove *move, edaMoveGen *gen, edaContinue *cont, 
	double temp, edaCoolingSchedule *coolschedule )
    : edaSA( move, gen, cont, temp, coolschedule )
  {}
  loSA( const loSA &sa )
    : edaSA( sa ) {}
  ~loSA() {
    sisEnd();
  }

  setClassID( _USERCLASSID_ + _CLSID_LO_SA_ );
};

class loTS: public edaTS {
public:
  loTS(): edaTS() {
    sisInit();
  }
  loTS( edaMove *move, edaMoveGen *next, 
	edaTabuList *tabulist, edaAspirCrit *aspircrit,
	edaContinue *contcrit )
    : edaTS( move, next, tabulist, aspircrit, contcrit ) {}
  loTS( edaMove *move, edaTSMoveExpl *moveexpl, edaContinue *contcrit )
    : edaTS( move, moveexpl, contcrit ) {}
  loTS( const loTS &ts)
    : edaTS( ts ) {}
  ~loTS() {
    sisEnd();
  }

  setClassID( _USERCLASSID_ + _CLSID_LO_TS_ );
};

void printHelp();

int main(int argc, char **argv)
{
  char scriptDir[256];
  char *blifFile = NULL;
  int c;
  int index;

  sprintf(scriptDir, ".");
  while ((c = getopt(argc, argv, "s:h?")) != -1 )
  {
    switch (c)
    {
      case 's':
        strncpy(scriptDir, optarg, 256);
        break;

      case '?':
      case 'h':
      default:
        printHelp();
        return 1;
    }
  }

  if (optind >= argc)
  {
    // There is no BLIF file
    printHelp();
    return 1;
  }

  blifFile = argv[optind];

  srand(time(NULL));

  // start SIS
  sisInit();

  // initial solution
  loNetList netlist( blifFile );;

  // hill climbing
  loScriptMove hcScript;
  if (scriptDir != NULL) {
    hcScript.setScriptDir( scriptDir );
  }
  loScriptMoveNext hcNext( 0, 8 );
  edaBestImprSelect hcSelect;
  loHC hcStart( &hcScript,
		&hcNext,
		&hcSelect );
  loHC hcEnd( &hcScript,
	      &hcNext,
	      &hcSelect );

  // simulated annealing
  RNG rng;
  rng.init();
  loScriptMove saScript;
  if (scriptDir != NULL) {
    saScript.setScriptDir( scriptDir );
  }
  loScriptMoveRandom saRandom( 0, 5 );
  edaExpCoolingSchedule saCooling(0.1+rng.uniform()*1e-1, 
				  0.7+rng.uniform()*1e-1);
  edaGenContinue saCont(10);
  loSA *sas[N];
  for( int i=0; i<N; i++ )
    sas[i] = new loSA( &saScript,
		       &saRandom,
		       &saCont,
		       500.0 + 300*rng.uniform(),
		       &saCooling );

  // tabu search
  loScriptMove tsScript;
  if (scriptDir != NULL) {
    tsScript.setScriptDir( scriptDir );
  }
  loScriptMoveNext tsNext( 0, 8 );
  edaSimpleSolutionTabuList tabuList;
  edaImprBestFitAspirCrit aspirCrit;
  edaGenContinue tsCont(10);
  loTS *tss[N];
  for( int i=0; i<N; i++ )
    tss[i] = new loTS( &tsScript, 
		       &tsNext, 
		       &tabuList, 
		       &aspirCrit, &tsCont );

  // search flow construction
  edaParWrapperControl sfControl;
  int hcStartId = sfControl.insertVertex( &hcStart );
  int hcEndId = sfControl.insertVertex( &hcEnd );
  int saIds[N], tsIds[N];
  for( int i=0; i<N; i++ ){
    saIds[i] = sfControl.insertVertex( sas[i] );
    tsIds[i] = sfControl.insertVertex( tss[i] );
  }
  for( int i=0; i<N; i++ ){
    sfControl.insertEdge( hcStartId, tsIds[i] ); 
    sfControl.insertEdge( tsIds[i], saIds[i] ); 
    sfControl.insertEdge( saIds[i], hcEndId ); 
  }

  // perform the search
  std::cout << "[From Fitness] " << netlist.fullEvaluate() << std::endl;
  sfControl.search( netlist );
  std::cout << "[To Fitness] " << netlist.fullEvaluate() << std::endl;

  // release memory
  for( int i=0; i<N; i++ ){
    delete sas[i];
    delete tss[i];
  }

  // end SIS
  sisEnd();

  return 0;
}

edaSerialize *userClassGenerate(int clsid)
{
  switch( clsid ){
  case _USERCLASSID_ + _CLSID_LO_NETLIST_:
    return new loNetList;
    
  case _USERCLASSID_ + _CLSID_SCRIPT_MOVE_:
    return new loScriptMove;
      
  case _USERCLASSID_ + _CLSID_SCRIPT_MOVE_RANDOM_:
    return new loScriptMoveRandom;
    
  case _USERCLASSID_ + _CLSID_SCRIPT_MOVE_NEXT_:
    return new loScriptMoveNext;
    
  case _USERCLASSID_ + _CLSID_LO_HC_:
    return new loHC;
    
  case _USERCLASSID_ + _CLSID_LO_SA_:
    return new loSA;
    
  case _USERCLASSID_ + _CLSID_LO_TS_:
    return new loTS;
    
  default:
    std::cerr << "Unknown classId " << clsid 
	      << " for object generation!!!" << std::endl;
    exit(-1);
  }
}

void printHelp()
{
  std::cerr << "Usage : ./loLargePFWSearch [-h] [-?] [-s <scripts dir>] <instance>" << std::endl;
  std::cerr << "  -h, -?             Print this help" << std::endl;
  std::cerr << "  -s <scripts dir>   Directory contains SIS scripts" << std::endl;
  std::cerr << "  <instance>         BLIF file to be optimize" << std::endl;
}
